/*
	ACDM_buffer.c

	provides support functions for controlling the VGA adapter and buffer generation/control
	only buffer-level functionality is handled in these functions

	Author: Aidan Goddard
*/

#include"ACDM_buffer.h"
#include"../../kernel/headers/scheduler_functions.h"
#include"../../kernel/headers/bakery_lock.h"
#include <stdarg.h>

///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
// external functions

extern void ClearBuff(char *buffer);
extern void ClearChars(char *buffer);
extern void ClearBuffLine(char *buffer);
extern void ClearCharsLine(char *buffer);
extern void BuffCopy(char *inbuff, char *outbuff);
extern void RibbonCopy(char *inbuff, char *outbuff);

///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
// tables & buffers

// screen buffers and controls
struct /*__attribute__((aligned(4096)))*/ ACDM_STATIC_BUFFER	// doesn't need to be 4096 aligned yet
{
	// the screen buffer
	uint8_t buff[3680];	// 23 lines of 80 chars and 80 attributes. lines 1 - 23 inclusive (0 and 24 are ribbons)
	uint8_t padding[4096 - 3680];
};

struct ACDM_BUFFER_CONTROL
{
	// hwnd of this buffer
	HWND hwnd;

	// PID of owner
	uint64_t owner_PID;

	// cursor control
	uint32_t cursor_on;		// 0 = off, 1 = on
	uint32_t cursor_pos;	// position of cursor within the buffer (0 - 1839)

	// window name
	char window_name[76];
};

// first 16 static buffers are reserved for internal use. base is located at 0x3e00000; 512 buffers
static struct ACDM_BUFFER_CONTROL *buffer_controls = (struct ACDM_BUFFER_CONTROL*)(KOFFSET + 0x4000000);
static struct ACDM_STATIC_BUFFER *static_buffers = (struct ACDM_STATIC_BUFFER*)(KOFFSET + 0x3e00000);

// ribbons. kept internal.
static char ribbon_top[160];
static char ribbon_bottom[160];

///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
// global control variables
static SCH_CLS buffer_lock;
static volatile int active_buffer;

///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
// secondary functions

// function to override the cursor position in a buffer
void SetCursor(int buffer, int pos, int state)
{
	buffer_controls[buffer].cursor_on = state;
	buffer_controls[buffer].cursor_pos = pos;
	return;
}

// function to create (init) a static buffer
void StaticBufferCreate(int buffer, HWND hwnd, uint64_t PID, char *window_name, int name_count)
{
	// clear the buffer
	ClearBuff((char*)&static_buffers[buffer].buff);

	// set defaults in control buffer
	buffer_controls[buffer].cursor_on = 0;
	buffer_controls[buffer].cursor_pos = 0;

	buffer_controls[buffer].hwnd = hwnd;
	buffer_controls[buffer].owner_PID = PID;

	// copy the name across
	int i;
	for(i = 0; (i < 76) && (i < name_count); i++)
	{
		buffer_controls[buffer].window_name[i] = window_name[i];
	}

	// finished
	return;
}

// function to get address of the static buffer page (4k-aligned)
uint64_t GetStaticBufferAddress(int buffer)
{
	return (uint64_t)&static_buffers[buffer];
}

// buffer clearing functions
void ClearBuffer(int buffer)
{
	// get a pointer to the buffer
	char *ptr = (char*)&static_buffers[buffer];

	// clear the buffer
	ClearBuff(ptr);

	// finished
	return;
}

void ClearCharacters(int buffer)
{
	// get a pointer to the buffer
	char *ptr = (char*)&static_buffers[buffer];

	// clear the buffer
	ClearChars(ptr);

	// finished
	return;
}

void ClearBufferLine(int buffer, int line)
{
	// get a pointer to the buffer
	char *ptr = (char*)&static_buffers[buffer];

	// get the line offset
	ptr += (line * 160);

	// clear the line
	ClearBuffLine(ptr);

	// finished
	return;
}

void ClearCharactersLine(int buffer, int line)
{
	// get a pointer to the buffer
	char *ptr = (char*)&static_buffers[buffer];

	// get the line offset
	ptr += (line * 160);

	// clear the line
	ClearCharsLine(ptr);

	// finished
	return;
}

///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
// video buffer functions

// function to switch the active buffer to a different one
void SwitchBuffer(int static_buffer)
{
	// get buffer lock
	__asm__ __volatile__("cli":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&buffer_lock, CPU);

	// change the active buffer
	active_buffer = static_buffer;

	// release buffer lock
	BakeryLeave(&buffer_lock, CPU);
	__asm__ __volatile__("sti":::"memory");

	// finished
	return;
}

// update the cursor position
static void UpdateCursor(uint32_t c_pos)
{
	// adjust pos for inside video buffer
	c_pos = c_pos + 80;

	// check if putting cursor outside of bounds ( > 2000)
	if(c_pos >= 2000)
	{
		//c_pos = 1999;
	}
	else
	{

	}

	// send low byte
	outb(0x3d4, 0x0f);
	outb(0x3d5, (uint8_t)(c_pos & 0x000000ff));

	// send high byte
	outb(0x3d4, 0x0e);
	outb(0x3d5, (uint8_t)((c_pos >> 8) & 0x000000ff));

	// finished
	return;
}

// function to update the video buffer
void UpdateVideoBuffer(int static_buffer)
{
	// get buffer lock
	__asm__ __volatile__("cli":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&buffer_lock, CPU);

	// grab a direct pointer to the buffers
	struct ACDM_STATIC_BUFFER *buffer = (struct ACDM_STATIC_BUFFER*)&static_buffers[static_buffer];
	struct ACDM_BUFFER_CONTROL *control = (struct ACDM_BUFFER_CONTROL*)&buffer_controls[static_buffer];

	// check if the buffer belongs to the currently active window
	if(active_buffer == static_buffer)
	{
		// is a static buffer
		// copy the buffer to the video buffer (fast copy)
		BuffCopy((char*)buffer->buff, (char*)(uint64_t)KOFFSET + 0xb8000 + 160);

		// check if cursor is active for this buffer
		if(control->cursor_on)
		{
			UpdateCursor(control->cursor_pos);
		}
		else
		{
			UpdateCursor(2000);	// should turn it off
		}
	}

	// release buffer lock
	BakeryLeave(&buffer_lock, CPU);
	__asm__ __volatile__("sti":::"memory");

	// finished
	return;
}

// function to update the ribbons
void UpdateRibbons()
{
	// get buffer lock
	__asm__ __volatile__("cli":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&buffer_lock, CPU);

	// copy the ribbon to the video buffer
	RibbonCopy(ribbon_top, (char*)(KOFFSET + 0xb8000));
	RibbonCopy(ribbon_bottom, (char*)(KOFFSET + 0xb8000 + 3840));

	// release buffer lock
	BakeryLeave(&buffer_lock, CPU);
	__asm__ __volatile__("sti":::"memory");

	// finished
	return;
}

void ChangeWindowName(int buffer, char *window_name, int name_count)
{
	// copy it to the static buffer
	char *dest = (char*)buffer_controls[buffer].window_name;
	int i;
	for(i = 0; i < name_count; i++)
	{
		if(window_name[i] == 0x00)
		{
			break;
		}
		else
		{
			dest[i] = window_name[i];
		}
	}

	// fill the rest with blanks
	for(; i < 76; i++)
	{
		dest[i] = 0;
	}

	// finished
	return;
}

///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
// buffer generation support functions

// write an unsigned integer
static int printiu(int char_offset, struct ACDM_STATIC_BUFFER *out_buffer, uint64_t value, int base)
{
	// check if 0
	if(value == 0)
	{
		out_buffer->buff[char_offset] = '0';
		return char_offset + 2;
	}

	// numbers array and string array
	char *numbers = "0123456789ABCDEF";
	char number[65];
	number[64] = 0x00;

	// some values
	uint64_t quotient = value;
	uint64_t remainder = 0;
	int i = 63;

	// get each character
	while(1)
	{
		// get quotient and remainder
		remainder = quotient % base;
		quotient = quotient / base;

		// check if finished
		if(!(remainder || quotient))
		{
			break;
		}

		// get the character
		number[i--] = numbers[remainder];

	}

	// print the string
	while(number[++i] != 0x00)
	{
		out_buffer->buff[char_offset] = number[i];
		char_offset += 2;
	}

	// finished
	return char_offset;
}

// print a signed integer
static int printi(int char_offset, struct ACDM_STATIC_BUFFER *out_buffer, int64_t value)
{
	// check if value is negative
	if(value < 0)
	{
		// print negative sign
		out_buffer->buff[char_offset] = '-';
		char_offset += 2;

		// multiply value by -1
		value = value * -1;
	}

	// print the number in base 10
	char_offset = printiu(char_offset, out_buffer, value, 10);

	// finished
	return char_offset;
}

// convert an address to a string (address has leading zeros included)
static char tmp_address[17];	// 16 chars with trailing nullchar

static char *address_to_string(uint64_t address)
{
	// number chars array
	char *numbers = "0123456789ABCDEF";

	// first setup string
	int i;
	for(i = 0; i < (sizeof(uint64_t) * 2); i++)
	{
		tmp_address[i] = '0';
	}

	// get start position (start at lowest significant portion)
	int k = sizeof(uint64_t) * 2;
	tmp_address[k] = 0x00;		// set nullchar

	// loop through, placing chars
	for(i = 0; i < (sizeof(uint64_t) * 2); i++)
	{
		// point to next char to fill
		k--;

		// get value for this nibble (4 bits)
		uint64_t value = (address >> (i * 4)) & 0x0f;

		// set the char
		tmp_address[k] = numbers[value];
	}

	return tmp_address;
}



///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
// buffer generation functions

// function to generate a 'subwindow' and return a pointer to the start of the client area
int GenerateSubWindow(int static_buffer, int start_offset, int width, int height)
{
	// grab a direct pointer to the buffer
	struct ACDM_STATIC_BUFFER *out_buffer = (struct ACDM_STATIC_BUFFER*)&static_buffers[static_buffer];

	// calculate limits and offsets
	int row_start = start_offset % 160;			// gives the relative offset for the start of each row
	int row_end = row_start + (width * 2);		// gives the relative offset for the end of each row
	int start_row = start_offset / 160;			// gives the starting row number
	int end_row = start_row + height;			// gives the ending row number

	// set the background formatting (0x70)
	int i, j, k, l;
	for(i = start_row; i <= end_row; i++)
	{
		//out_buffer->buff[i * 160 + 1] = 0x71;
		k = (i * 160) + row_end;
		for(j = (i * 160) + row_start + 1; j < k; j += 2)
		{
			out_buffer->buff[j] = 0x70;
			out_buffer->buff[j - 1] = 0;
		}
	}

	// generate horizontal borders
	j = start_row * 160 + row_end - 2;
	for(i = start_row * 160 + row_start + 2; i < j; i += 2)
	{
		out_buffer->buff[i] = 0xc4;
	}
	j = end_row * 160 + row_end - 2;
	for(i = end_row * 160 + row_start + 2; i < j; i += 2)
	{
		out_buffer->buff[i] = 0xc4;
	}

	// generate vertical borders
	j = end_row * 160 + row_start;
	for(i = (start_row + 1) * 160 + row_start; i < j; i += 160)
	{
		out_buffer->buff[i] = 0xb3;
	}
	j = end_row * 160 + row_end - 2;
	for(i = (start_row + 1) * 160 + row_end - 2; i < j; i += 160)
	{
		out_buffer->buff[i] = 0xb3;
	}

	// generate corners
	i = start_row * 160 + row_start;
	j = end_row * 160 + row_start;
	k = start_row * 160 + row_end - 2;
	l = end_row * 160 + row_end - 2;
	out_buffer->buff[i] = 0xda;
	out_buffer->buff[j] = 0xc0;
	out_buffer->buff[k] = 0xbf;
	out_buffer->buff[l] = 0xd9;

	// generate shadow (black on black)
	// horizontal
	j = (end_row + 1) * 160 + row_end;
	for(i = (end_row + 1) * 160 + row_start + 2; i <= j; i += 2)
	{
		out_buffer->buff[i + 1] = 0x00;
	}

	// vertical
	j = end_row * 160 + row_end + 2;
	for(i = (start_row + 1) * 160 + row_end; i < j; i += 160)
	{
		out_buffer->buff[i + 1] = 0x00;
	}

	// return the client area start
	return start_offset + 162;
}

// function to generate a string and write it directly to the specified
int WriteStringToBuffer(int static_buffer, int char_offset, char *string, ...)
{
	// grab a direct pointer to the buffer
	struct ACDM_STATIC_BUFFER *out_buffer = (struct ACDM_STATIC_BUFFER*)&static_buffers[static_buffer];

	// sort out variable arguments list
	va_list va;
	va_start(va, string);

	int i;
	for(i = 0; string[i] != 0x00; i++)
	{
		if(string[i] == '%')
		{
			i++;
			if(string[i] == '%')
			{
				out_buffer->buff[char_offset] = '%';
				char_offset += 2;
			}
			else if(string[i] == 'c')
			{
				out_buffer->buff[char_offset] = (char)va_arg(va, int);
				char_offset += 2;
			}
			else if(string[i] == 'd' || string[i] == 'i')
			{
				char_offset = printi(char_offset, out_buffer, va_arg(va, int64_t));
			}
			else if(string[i] == 'o')
			{
				char_offset = printiu(char_offset, out_buffer, va_arg(va, uint64_t), 8);
			}
			else if(string[i] == 'u')
			{
				char_offset = printiu(char_offset, out_buffer, va_arg(va, uint64_t), 10);
			}
			else if(string[i] == 'x')
			{
				char_offset = printiu(char_offset, out_buffer, va_arg(va, uint64_t), 16);
			}
			else if(string[i] == 'b')
			{
				char_offset = printiu(char_offset, out_buffer, va_arg(va, uint64_t), 2);
			}
			else if(string[i] == 's')
			{
				char *the_string = (char*)va_arg(va, uint64_t);
				int k;

				for(k = 0; the_string[k] != 0x00; k++)
				{
					out_buffer->buff[char_offset] = the_string[k];
					char_offset += 2;
				}
			}
			else if(string[i] == 'a')
			{
				uint64_t address = va_arg(va, uint64_t);
				char *address_string = address_to_string(address);
				int k;

				for(k = 0; address_string[k] != 0x00; k++)
				{
					out_buffer->buff[char_offset] = address_string[k];
					char_offset += 2;
				}
			}
			else if(string[i] == 'f')
			{
				double value = va_arg(va, double);

				// print non-fraction part and decimal point
				char_offset = WriteStringToBuffer(static_buffer, char_offset, "%i.", (int64_t)value);
				if(value < 0)
				{
					value = -value;
				}

				// print fraction part
				value = value - (int)value;
				int k;

				for(k = 0; k < 10; k++)
				{
					value = value * 10;
					if(((int)value) == 0)
					{
						out_buffer->buff[char_offset] = '0';
						char_offset += 2;
					}

					int test = (value - (int)value) * 1000000000;
					if(test == 0)
					{
						break;
					}
				}

				char_offset = WriteStringToBuffer(static_buffer, char_offset, "%i", (int)value);


			}

			else
			{
				continue;
			}
		}
		else
		{
			out_buffer->buff[char_offset] = string[i];
			char_offset += 2;
		}
	}

	va_end(va);

	// finished
	return char_offset;
}

///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
// ribbon generation functions

void RibbonSetup()
{
	// set up the ribbons
	// formatting
	int i;
	for(i = 3; i < 158; i += 2)
	{
		// main ribbon colour - black text on light blue
		ribbon_top[i] = 0x30;
		ribbon_bottom[i] = 0x30;
	}

	// ribbon corners - white text on dark blue
	ribbon_top[1] = 0x1f;
	ribbon_top[159] = 0x1f;
	ribbon_bottom[1] = 0x1f;
	ribbon_bottom[159] = 0x1f;

	// ribbon corner chars - box horizontal
	ribbon_top[0] = 0xcd;
	ribbon_top[158] = 0xcd;
	ribbon_bottom[0] = 0xcd;
	ribbon_bottom[158] = 0xcd;

	// finished
	return;
}

static inline void ConvertNumToShortChar(char *buffer, uint32_t num)
{
	// define a char array to use
	const char *nums = "0123456789";

	// convert number to 2 character char in base 10
	// check number for size < 10000
	if(num > 9999)
	{
		num = 9999;
	}

	// set buffer defaults
	buffer[0] = '0';
	buffer[1] = '0';

	// check for zero
	if(num == 0)
	{
		buffer[2] = 0;
		return;
	}

	// now generate the number
	buffer[0] = nums[num % 10];
	num = num / 10;
	if(num == 0)
	{
		//buffer[1] = 0;
		return;
	}
	buffer[1] = nums[num % 10];
	num = num / 10;
	if(num == 0)
	{
		buffer[2] = 0;
		return;
	}
	buffer[2] = nums[num % 10];
	num = num / 10;
	if(num == 0)
	{
		//buffer[3] = 0;
		return;
	}
	buffer[3] = nums[num % 10];
	buffer[4] = 0;


	// finished
	return;
}

void UpdateRibbonTimeDate(int hours, int minutes, int day, int month, int year)
{
	// create a 3-char buffers
	char hour_buffer[3];
	char min_buffer[3];
	char day_buffer[3];
	char month_buffer[3];
	char year_buffer[5];

	// get strings for each
	ConvertNumToShortChar(hour_buffer, hours);
	ConvertNumToShortChar(min_buffer, minutes);
	ConvertNumToShortChar(day_buffer, day);
	ConvertNumToShortChar(month_buffer, month);
	ConvertNumToShortChar(year_buffer, year);

	// set chars for each number
	int i, k;
	char *templ = "|   :     /  /    ";
	for(i = 0, k = 120; templ[i] != 0; i++, k += 2)
	{
		ribbon_bottom[k] = templ[i];
	}
	for(i = 1, k = 124; i >= 0; i--, k += 2)
	{
		ribbon_bottom[k] = hour_buffer[i];
	}
	for(i = 1, k = 130; i >= 0; i--, k += 2)
	{
		ribbon_bottom[k] = min_buffer[i];
	}
	for(i = 1, k = 136; i >= 0; i--, k += 2)
	{
		ribbon_bottom[k] = day_buffer[i];
	}
	for(i = 1, k = 142; i >= 0; i--, k += 2)
	{
		ribbon_bottom[k] = month_buffer[i];
	}
	for(i = 3, k = 148; i >= 0; i--, k += 2)
	{
		ribbon_bottom[k] = year_buffer[i];
	}

	// finished
	return;
}

void UpdateRibbonCapsLock(int state)
{
	// check state
	if(state == 0)
	{
		// switch is off
		// set 3rd char's formatting to match background
		ribbon_top[150] = 0;
		ribbon_top[151] = 0x30;
	}
	else
	{
		// switch is on
		// set 5th char and formatting
		ribbon_top[150] = 'C';
		ribbon_top[151] = 0x74;
	}

	// finished
	return;
}

void UpdateRibbonScrollLock(int state)
{
	// check state
	if(state == 0)
	{
		// switch is off
		// set 5th char's formatting to match background
		ribbon_top[152] = 0;
		ribbon_top[153] = 0x30;
	}
	else
	{
		// switch is on
		// set 5th char and formatting
		ribbon_top[152] = 'S';
		ribbon_top[153] = 0x74;
	}

	// finished
	return;
}

void UpdateRibbonNumLock(int state)
{
	// check state
	if(state == 0)
	{
		// switch is off
		// set 5th char's formatting to match background
		ribbon_top[154] = 0;
		ribbon_top[155] = 0x30;
	}
	else
	{
		// switch is on
		// set 5th char and formatting
		ribbon_top[154] = 'N';
		ribbon_top[155] = 0x74;
	}

	// finished
	return;
}

void UpdateBottomRibbonText(char *text)
{
	// copy the text up to the limit (50 chars) to the bottom ribbon
	int i, k;
	const char *pre = "F1=Help | ";
	for(i = 0, k = 4; i < 10; i++, k += 2)
	{
		// no null-char, copy the character
		ribbon_bottom[k] = pre[i];
	}

	for(i = 0, k = 24; i < 48; i++, k += 2)
	{
		// check for null-char
		if(text[i] == 0x00)
		{
			break;
		}
		else
		{
			// no null-char, copy the character
			ribbon_bottom[k] = text[i];
		}
	}

	// need to zero the rest of them
	// i and k are left over from last for-loop
	for(; i < 48; i++, k += 2)
	{
		ribbon_bottom[k] = 0;
	}

	// finished
	return;
}

void UpdateWindowName(char *text)
{
	// copy the text up to the limit (60 chars) to the top ribbon
	int i, k;
	const char *pre = "| ";
	for(i = 0, k = 4; i < 2; i++, k += 2)
	{
		ribbon_top[k] = pre[i];
		ribbon_top[k + 142] = pre[i];
	}

	for(i = 0, k = 8; i < 60; i++, k += 2)
	{
		// check for null-char
		if(text[i] == 0x00)
		{
			// is end of string, break
			break;
		}
		else
		{
			// no null-char, copy the character
			ribbon_top[k] = text[i];
		}
	}

	// need to zero the rest of them
	// i and k are left over from last for-loop
	for(; i < 60; i++, k += 2)
	{
		ribbon_top[k] = 0;
	}

	// finished
	return;
}

///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
// buffer querying

HWND BufferGetHWND(int buffer)
{
	return buffer_controls[buffer].hwnd;
}

uint64_t BufferGetPID(int buffer)
{
	return buffer_controls[buffer].owner_PID;
}

char *GetWindowName(int buffer)
{
	return buffer_controls[buffer].window_name;
}
